﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

using System.Collections.Generic;

namespace ManPowerEngine
{
	/// <summary>
	/// Collision types
	/// </summary>
	public enum CollisionType
	{
		/// <summary>
		/// box to box collision.
		/// </summary>
		BOXBOX,
		/// <summary>
		/// bitmap to box collision: bitmap collider will always be the first collider in the list.
		/// </summary>
		BITMAPBOX,
		/// <summary>
		/// Skip this collision
		/// </summary>
		SKIP,
		/// <summary>
		/// Unknown collision type. No action will be taken.
		/// </summary>
		UNKNOWN
	}
	/// <summary>
	/// Parameters of a collision event
	/// </summary>
	public class Collision
	{
		/// <summary>
		/// The type of this collision
		/// </summary>
		public CollisionType Type;
		/// <summary>
		/// Current collider of this collision
		/// </summary>
		public ColliderBase This;
		/// <summary>
		/// The other collider of this collision
		/// </summary>
		public ColliderBase Other;
		/// <summary>
		/// The point of this collision, in world coordination
		/// </summary>
		public Vector Point;
	}
	/// <summary>
	/// Physics System of MPE
	/// </summary>
	public class PhysicsSystem
	{
		/// <summary>
		/// how many pixels for one meter.
		/// </summary>
		public static double PixelPerMeter = 16;

		/// <summary>
		/// G value for gravity
		/// </summary>
		public static double G = 9.8;

		private static PhysicsSystem msPhysicsSystem;
		/// <summary>
		/// All rigidbodies registered in game object hierarchy
		/// </summary>
		private List<Rigidbody> mRegisteredRigidbodies;
		/// <summary>
		/// All colliders registered in game object hierarchy
		/// </summary>
		private List<ColliderBase> mRegisteredColliders;
		/// <summary>
		/// How many iterations of physics simulation will be done for each frame
		/// </summary>
		private int mIterationPerFrame;
		/// <summary>
		/// Initialize Physics System
		/// </summary>
		/// <param name="iterations">How many iterations of physics simulation will be performed for each frame</param>
		internal PhysicsSystem(int iterations)
		{
			msPhysicsSystem = this;
			mIterationPerFrame = iterations;
			mRegisteredRigidbodies = new List<Rigidbody>();
			mRegisteredColliders = new List<ColliderBase>();
		}

		/// <summary>
		/// get the instance of physics system
		/// </summary>
		public static PhysicsSystem Instance
		{
			get { return msPhysicsSystem; }
		}

		/// <summary>
		/// do certain amount of iterations of physics simulation for all physics objects
		/// </summary>
		internal void SimulatePhysics(TimeArgs Time)
		{
			// assume this frame requires the same amount of time as the last frame
			double dt = (Time.SinceLastFrame > 0.125 ? 0.125 : Time.SinceLastFrame) / mIterationPerFrame;
			// add everything currently in the hierarchy into rigidbody list
			_AddGameObjectIntoList(GameObject.Root);

			// do certain times of simulation iterations
			for (int i = 0; i < mIterationPerFrame; i++)
			{
				PhysicsIteration(dt);
			}
			// clean the list after physics engine
			mRegisteredRigidbodies.Clear();
			mRegisteredColliders.Clear();
		}

		private void _AddGameObjectIntoList(GameObject go)
		{
			if (!go.Enabled)
				return;
			// recursively add all renderers into the list
			if (go.Rigidbody != null)
			{
				mRegisteredRigidbodies.Add(go.Rigidbody);
			}
			if (go.Collider != null)
			{
				if (go.Collider._OnScreenCheck())
					mRegisteredColliders.Add(go.Collider);
			}
			// visit all children
			foreach (GameObject child in go.Children)
			{
				_AddGameObjectIntoList(child);
			}
		}

		/// <summary>;
		/// Do one iteration of physics simulation
		/// </summary>
		/// <param name="dt">Time passed for this iteration</param>
		private void PhysicsIteration(double dt)
		{
			List<Collision> rigidbodyCollisions = new List<Collision>();
			// retrivial all colliders we've got.
			int collidecount = mRegisteredColliders.Count;
			for (int i = 0; i < collidecount; i++)
			{
				ColliderBase collider1 = mRegisteredColliders[i];
				for (int j = i + 1; j < collidecount; j++)
				{
					ColliderBase collider2 = mRegisteredColliders[j];
					CollisionType type = DecideCollisionType(ref collider1, ref collider2);
					Vector? result;
					switch (type)
					{
						case CollisionType.BOXBOX:
							result = CollideBoxToBox((BoxCollider)collider1, (BoxCollider)collider2);
							if (result != null)
							{
								Collision collision = new Collision
								{
									This = collider2,
									Other = collider1,
									Point = (Vector)result,
									Type = type
								};
								collider2._OnCollide(collision);
								collision.This = collider1;
								collision.Other = collider2;
								collider1._OnCollide(collision);
								if (collider1.GameObject.Rigidbody != null && collider2.GameObject.Rigidbody != null)
								{
									rigidbodyCollisions.Add(collision);
								}
							}
							break;
						case CollisionType.BITMAPBOX:
							result = CollideBitmapToBox((BitmapCollider)collider1, (BoxCollider)collider2);
							if (result != null)
							{
								Collision collision = new Collision
								{
									This = collider2,
									Other = collider1,
									Point = (Vector)result,
									Type = type
								};
								collider2._OnCollide(collision);
								// switch them and send out again
								collision.This = collider1;
								collision.Other = collider2;
								collider1._OnCollide(collision);
								if (collider2.GameObject.Rigidbody != null && collider1.GameObject.Rigidbody == null)	// bitmap colliders won't have the rigidbody
								{
									rigidbodyCollisions.Add(collision);
								}
							}
							break;
						case CollisionType.SKIP:
							break;
						default:
							Log.Debug("Unknown");
							break;
					}
				}
			}

			// update forces into velocity
			foreach (Rigidbody rigidbody in mRegisteredRigidbodies)
			{
				rigidbody.UpdateForce(dt);
			}

			// collision responses
			foreach (Collision collision in rigidbodyCollisions)
			{
				// solve different types of rigidbody collisions
				switch (collision.Type)
				{
					case CollisionType.BOXBOX:
						// box to box collision
						GameObject g1 = collision.This.GameObject;
						GameObject g2 = collision.Other.GameObject;
						// center position
						Vector cp1 = g1.Transform.Position;
						Vector cp2 = g2.Transform.Position;
						Vector n = cp2 - cp1;
						n.Normalize();
						// distance from hit point to center
						Vector r1 = collision.Point - cp1;
						Vector r2 = collision.Point - cp2;
						Vector vp1 = g1.Rigidbody.PointVelocity(r1);
						Vector vp2 = g2.Rigidbody.PointVelocity(r2);
						double vrMag;
						Vector vr = (g2.Rigidbody.Velocity - g1.Rigidbody.Velocity).Project(n, out vrMag);
						double r1n = Vector.CrossProduct(r1, n);
						double r2n = Vector.CrossProduct(r2, n);
						double e = 1;

						double I = (e + 1) * vrMag / ((1 / g1.Rigidbody.Mass) + (1 / g2.Rigidbody.Mass) + (r1n / g1.Rigidbody.Inertia) + (r2n / g2.Rigidbody.Inertia));
						g1.Rigidbody.AddForce(-n * I / dt, r1);
						g2.Rigidbody.AddForce(n * I / dt, r2);
						break;
					case CollisionType.BITMAPBOX:
						// collision.This is bitmap, collision.Other is box
						Vector localpos = collision.Point - collision.This.GameObject.Transform.Position;
						int centerx = (int)localpos.X;
						int centery = (int)localpos.Y;
						int counter = 0;
						Vector acc = Vector.Zero;
						ImageRenderer img = collision.This.GameObject.Renderer as ImageRenderer;
						int r;
						for (r = 1; r < 6; r++)
						{
							// left/right border
							for (int rely = -r; rely <= r; rely++)
							{
								CheckBitmapPoint(centerx, centery, -r, rely, img, ref counter, ref acc);
								CheckBitmapPoint(centerx, centery, r, rely, img, ref counter, ref acc);
							}
							// top/bottom border
							for (int relx = -r + 1; relx <= r - 1; relx++)
							{
								CheckBitmapPoint(centerx, centery, relx, -r, img, ref counter, ref acc);
								CheckBitmapPoint(centerx, centery, relx, r, img, ref counter, ref acc);
							}
							if (counter > 0)
							{
								break;
							}
						}
						if (counter > 0)
						{
							// just brounce back
							acc.Normalize();
							Vector vel = collision.Other.GameObject.Rigidbody.Velocity;
							double projectmag;
							Vector normalproject = vel.Project(acc, out projectmag);
							if (projectmag < 0)
							{
								vel -= normalproject * 2;
								collision.Other.GameObject.Rigidbody.Velocity = vel;
							}
						}
						break;
				}
			}
			foreach (Rigidbody rigidbody in mRegisteredRigidbodies)
			{
				rigidbody.UpdatePosition(dt);
			}
		}

		private void CheckBitmapPoint(int centerx, int centery, int relx, int rely, ImageRenderer img, ref int counter, ref Vector acc)
		{
			int x = centerx + relx;
			int y = centery + rely;
			if (x < 0 || y < 0 || x >= img.Width || y >= img.Height)
				return;
			if (img.GetPixel(x, y).R != 0)
			{
				counter++;
				Vector n = new Vector(relx, rely);
				n.Normalize();
				acc += n;
			}
		}

		private CollisionType DecideCollisionType(ref ColliderBase c1, ref ColliderBase c2)
		{
			if (c1 is BoxCollider)
			{
				if (c2 is BoxCollider)
				{
					return CollisionType.BOXBOX;
				}
				else if (c2 is BitmapCollider)
				{
					ColliderBase tmp = c1;
					c1 = c2;
					c2 = tmp;
					return CollisionType.BITMAPBOX;
				}
			}
			else if (c1 is BitmapCollider)
			{
				if (c2 is BoxCollider)
				{
					return CollisionType.BITMAPBOX;
				}
				else if (c2 is BitmapCollider)
				{
					return CollisionType.SKIP;
				}
			}
			return CollisionType.UNKNOWN;
		}

		/// <summary>
		/// Box-Box collide detection
		/// </summary>
		/// <param name="c1">box 1</param>
		/// <param name="c2">box 2</param>
		/// <returns>Detection result</returns>
		private Vector? CollideBoxToBox(BoxCollider c1, BoxCollider c2)
		{
			// c1 has points in c2?
			c1.GeneratePoints();
			c2.GenerateBounds();
			foreach (Vector point in c1.iPoints)
			{
				if (c2.PointCheck(point))
				{
					return point;
				}
			}
			// c2 has points in c1?
			c2.GeneratePoints();
			c1.GenerateBounds();
			foreach (Vector point in c2.iPoints)
			{
				if (c1.PointCheck(point))
				{
					return point;
				}
			}
			return null;
		}

		/// <summary>
		/// Bitmap-Box collide detection
		/// </summary>
		/// <param name="c1">bitmap collider</param>
		/// <param name="c2">box collider</param>
		/// <returns>Detection result</returns>
		private Vector? CollideBitmapToBox(BitmapCollider c1, BoxCollider c2)
		{
			// TODO: check bitmap to box
			c1.GenerateBounds();
			c2.GeneratePoints();
			Vector bitmappos = c1.GameObject.Transform.Position;
			foreach (Vector point in c2.iPoints)
			{
				if (c1.PointCheck(point))
				{
					// Calculate localposition in c1
					Vector localpos = (point - bitmappos); //.Rotate(-bitmaprot);
					Color color = (c1.GameObject.Renderer as ImageRenderer).GetPixel(localpos);
					if (color.R == 0)
					{
						return point;
					}
				}
			}
			return null;
		}
	}
}
